//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#include "Platforms/Engine.Platforms.h"
#include "Base/Utils/Log/Logger.h"

namespace Engine
{

/*
=================================================
	__engine_write_to_log
=================================================
*/
	inline static void __engine_write_to_console (const char *msg, ELog::type type, const char *file, int line)
	{
		DEBUG_ONLY(
			switch ( type )
			{
				case ELog::INFO :
					__DebugConsoleWrite_Info( file, line, msg );
					break;

				case ELog::WARN :
					__DebugConsoleWrite_Warning( file, line, msg );
					break;

				case ELog::ERROR :
				case ELog::FATAL :
					__DebugConsoleWrite_Error( file, line, msg );
					break;

				default :
					__DebugConsoleWrite( file, line, msg );
			}
		)
	}

/*
=================================================
	__engine_write_to_log
=================================================
*/
	void __engine_write_to_log (const char *msg, ELog::type type, const char *file, int line)
	{
		if ( Base::Logger::GetInstance() == null ) {
			__engine_write_to_console( msg, type, file, line );
			return;
		}

		Base::Logger::GetInstance()->Write( msg, type, file, line );
	}


namespace Base
{
	Logger *	Logger::_instance = null;


/*
=================================================
	constructor
=================================================
*/
	Logger::Logger (const EngineSubSystemsRef ess) :
		BaseObject(ess)
	{
		_instance = this;

		_buffer.Reserve( 256 );
	}
	
/*
=================================================
	destructor
=================================================
*/
	Logger::~Logger ()
	{
		ASSERT_EXT( _logFile.IsNull(), "Log file must be closed!" );
		ASSERT_EXT( _crashFileName.Empty(), "Crash file must be closed!" );

		_instance = null;
	}
	
/*
=================================================
	Open
=================================================
*/
	bool Logger::Open (uni_c_string name, bool unique)
	{
		Close();


		string	log_name;

		log_name.Reserve( 256 );

		// create directory
		log_name = FileAddressUtils::GetPath( name );

		if ( not ESS()->GetFileSystem()->IsDirectoryExist( log_name ) )
		{
			CHECK_ERR( ESS()->GetFileSystem()->CreateDirectories( log_name ) );
		}

		if ( unique )
		{
			for (uint i = 0; i < 100; ++i)
			{
				log_name.ClearMem();

				log_name << name << "_(";

				_GetDateForFName( log_name );

				log_name << ')';
				
				if ( i != 0 )
					log_name << '_' << i;

				log_name << ".html";

				if ( not ESS()->GetFileSystem()->IsFileExist( log_name ) )
					break;
			}
		}
		else
		{
			log_name.ClearMem();
			log_name << name << ".html";
		}
		

		SCOPELOCK( _lockLog );
		CHECK_ERR( ESS()->GetFileSystem()->CreateFile( log_name, _logFile ) );

		static const static_string<256>	header( "<html> <head> <title> log </title> </head> <body BGCOLOR=\"#ffffff\">"
												"<p><PRE><font face=\"Lucida Console, Times New Roman\""
												"size=\"2\" color=\"#000000\">\n" );

		_logFile->Write( header.cstr(), header.Length() );
		return true;
	}
	
/*
=================================================
	Close
=================================================
*/
	void Logger::Close ()
	{
		SCOPELOCK( _lockLog );

		if ( _logFile.IsNull() )
			return;

		static const static_string<64>	header( "</font></PRE> </p> </body> </html>\n" );
		
		_logFile->Write( header.cstr(), header.Length() );

		_logFile->Close();
		_logFile = null;
	}
	
/*
=================================================
	CreateCrashFile
=================================================
*/
	bool Logger::CreateCrashFile (uni_c_string name)
	{
		CloseCrashFile();

		string	crash_file;

		crash_file.Reserve( 256 );
		crash_file = FileAddressUtils::GetPath( name );

		if ( not ESS()->GetFileSystem()->IsDirectoryExist( crash_file ) )
		{
			CHECK_ERR( ESS()->GetFileSystem()->CreateDirectories( crash_file ) );
		}

		crash_file.ClearMem();
		crash_file << name << ".crash";

		WFilePtr	file;

		CHECK_ERR( ESS()->GetFileSystem()->CreateFile( crash_file, file ) );

		string	msg;

		msg << "Started at ";	_GetDate( msg );
		msg << " - ";

		CHECK_ERR( file->Write( msg.cstr(), msg.Length() ) );

		_crashFileName = crash_file;
		return true;
	}
	
/*
=================================================
	CloseCrashFile
=================================================
*/
	bool Logger::CloseCrashFile ()
	{
		if ( _crashFileName.Empty() )
			return true;
		
		WFilePtr	file;

		CHECK_ERR( ESS()->GetFileSystem()->OpenForAppend( _crashFileName, file ) );
		
		string	msg;

		msg << "Closed at ";	_GetDate( msg );
		msg << "\n";

		CHECK_ERR( file->Write( msg.cstr(), msg.Length() ) );

		_crashFileName.ClearMem();
		return true;
	}
	
/*
=================================================
	ReadCrashFile
=================================================
*/
	bool Logger::ReadCrashFile (uni_c_string name, OUT bool &wasCrashed, OUT Date &time) const
	{
		wasCrashed	= false;
		time		= Date();

		string	fname = name;

		if ( FileAddressUtils::GetExtension( fname ).Empty() ) {
			fname << ".crash";
		}

		if ( not _crashFileName.Empty() ) {
			CHECK_ERR( _crashFileName != fname );
		}
		

		RFilePtr	file;

		if ( not ESS()->GetFileSystem()->OpenForRead( fname, file ) )
			return false;

		string	data;
		usize	size = file->Size();

		CHECK_ALLOC( data.Reserve( size + 1 ) );

		CHECK_ERR( file->Read( data.ptr(), size ) );
		data.SetLength( size );

		usize	pos = 0;
		bool	start_found = false;

		while ( data.Find( "Started", pos, pos ) )	{ start_found = true; ++pos; }

		if ( start_found and not data.Find( "Closed", pos, pos ) )
		{
			wasCrashed = true;
		}

		return true;
	}
	
/*
=================================================
	SendAnEmail
=================================================
*/
	void Logger::SendAnEmail ()
	{
		TODO( "SendEmail" );
		//ESS()->GetApplication()->GetPlatform()->SendEmail();
	}
	
/*
=================================================
	Write
=================================================
*/
	void Logger::Write (uni_c_string msg, ELog::type type, uni_c_string file, int line)
	{
		SCOPELOCK( _lockLog );

		string &	str = _buffer;

		str.ClearMem();

		str << _GetPrefix( type ) << " - ";

		_GetDate( str );

		str << " - [" << string().FormatI( Thread::GetCurrentThreadId(), 16 ) << "]\t- " << msg;
		
		__engine_write_to_console( msg.cstr(), type, file.cstr(), line );
		
		if ( _logFile.IsNull() )
			return;

		string color_str;
		_AddColor( color_str, type );
		str.Insert( color_str, 0 );

		_AddColor( str, ELog::_SRC_FILE );

		str << "\n\t\t\t\t- (file: " << _GetFilePath( file, 2 ) << ", line: " << line << ")\n";

		_logFile->Write( str.cstr(), str.Length() );
	}

/*
=================================================
	_AddColor
=================================================
*/
	inline void Logger::_AddColor (INOUT string &str, ELog::type type)
	{
		enum EColor
		{
			BLUE		= 0x0000FF,
			NAVY		= 0x0000AB,
			VIOLET		= 0x8A2BE2,

			LIME		= 0x32CD32,
			GREEN		= 0x688E23,
			DARCK_GREEN	= 0x007F00,

			ORANGE		= 0xFF7F00,
			RED			= 0xFF0000,
			//RED			= 0xAA0000,
			DARCK_RED	= 0xA00000,

			SILVER		= 0xC0C0C0,
		};

		uint	color = 0;

		switch ( type )
		{
			case ELog::DEBUG		:	color = NAVY;			break;
			case ELog::INFO			:	color = DARCK_GREEN;	break;
			case ELog::WARN			:	color = ORANGE;			break;
			case ELog::ERROR		:	color = RED;			break;
			case ELog::FATAL		:	color = DARCK_RED;		break;
			case ELog::_SRC_FILE	:	color = SILVER;			break;
		}
		
		str << "</font><font color=\"#" << string().FormatAlignedI( color, 6, 16 ) << "\">";
	}
	
/*
=================================================
	_GetPrefix
=================================================
*/
	inline char Logger::_GetPrefix (ELog::type type)
	{
		switch ( type )
		{
			case ELog::DEBUG	:	return 'D';
			case ELog::INFO		:	return 'I';
			case ELog::WARN		:	return 'W';
			case ELog::ERROR	:	return 'E';
			case ELog::FATAL	:	return 'F';
		}
		return ' ';
	}

/*
=================================================
	_GetDate
----
	add date to string, format: "yyyy/mm/dd - hh:mm:ss"
=================================================
*/
	inline void Logger::_GetDate (INOUT string &str)
	{
		Date	date;
		date.Now();

#		define ADD_VALUE( _value )	(_value < 10 ? '0' : '\0') << _value

		str << ADD_VALUE( date.month ) << '/' << ADD_VALUE( date.day ) << " - "
			<< ADD_VALUE( date.hour ) << ':' << ADD_VALUE( date.minute ) << ':' << ADD_VALUE( date.second );

#		undef ADD_VALUE
	}
	
/*
=================================================
	_GetDateForFName
----
	add date to string, format: "yyyy.mm.dd-hh.mm.ss"
=================================================
*/
	inline void Logger::_GetDateForFName (INOUT string &str)
	{
		Date	date;
		date.Now();

#		define ADD_VALUE( _value )	(_value < 10 ? '0' : '\0') << _value

		str << ADD_VALUE( date.day )  << '.' << ADD_VALUE( date.month )  << '.' << date.year << '-'
			<< ADD_VALUE( date.hour ) << '.' << ADD_VALUE( date.minute );

#		undef ADD_VALUE
	}
	
/*
=================================================
	_GetFilePath
=================================================
*/
	inline uni_c_string Logger::_GetFilePath (uni_c_string str, uint depth)
	{
		int d = Max( 0u, depth );

		for (usize i = str.Length()-1; i < str.Length(); --i) {
			if ( (str[i] == '/' or str[i] == '\\') and (--d < 0) )
				return uni_c_string( (const char *)( str.cstr() + i+1 ), str.Length() - i-1 );
		}
		return str;
	}


}	// Base
}	// Engine